﻿using Aeon.Emulator.Sound.FM.Operators;

namespace Aeon.Emulator.Sound.FM.Channels
{
    /// <summary>
    /// Emulates a 4-operator OPL channel.
    /// </summary>
    internal sealed class Channel4 : Channel
    {
        private readonly StandardOperator op1;
        private readonly StandardOperator op2;
        private readonly StandardOperator op3;
        private readonly StandardOperator op4;
        private double previousFeedback;
        private double currentFeedback;

        /// <summary>
        /// Initializes a new instance of the Channel4 class.
        /// </summary>
        /// <param name="baseAddress">Base address of the channel's registers.</param>
        /// <param name="o1">First operator in the channel.</param>
        /// <param name="o2">Second operator in the channel.</param>
        /// <param name="o3">Third operator in the channel.</param>
        /// <param name="o4">Fourth operator in the channel.</param>
        /// <param name="opl">FmSynthesizer instance which owns the channel.</param>
        public Channel4(int baseAddress, StandardOperator o1, StandardOperator o2, StandardOperator o3, StandardOperator o4, FmSynthesizer opl)
            : base(baseAddress, opl)
        {
            this.op1 = o1;
            this.op2 = o2;
            this.op3 = o3;
            this.op4 = o4;
        }

        protected override double GetChannelOutput()
        {
            double channelOutput = 0;
            double op1Output = 0;

            var feedbackOutput = (this.previousFeedback + this.currentFeedback) / 2.0;
            
            int count = (this.OperatorCount << 1) | (this.opl.Registers[this.BaseAddress + 0xC3] & 0x1);
            switch(count)
            {
            default:
            case 0:
                if(op4.AdsrState != AdsrState.Off)
                {
                    op1Output = this.op1.GetOperatorOutput(feedbackOutput);
                    var op2Output = this.op2.GetOperatorOutput(op1Output * 4);
                    var op3Output = this.op3.GetOperatorOutput(op2Output * 4);
                    channelOutput = this.op4.GetOperatorOutput(op3Output * 4);
                }
                else
                    return 0;
                break;

            case 1:
                if((this.op2.AdsrState | this.op4.AdsrState) != AdsrState.Off)
                {

                    op1Output = this.op1.GetOperatorOutput(feedbackOutput);
                    var op2Output = this.op2.GetOperatorOutput(op1Output * 4);
                    var op3Output = this.op3.GetOperatorOutput();
                    var op4Output = this.op4.GetOperatorOutput(op3Output * 4);

                    channelOutput = (op2Output + op4Output) / 2;
                }
                else
                    return 0;
                break;

            case 2:
                if((this.op1.AdsrState | this.op4.AdsrState) != AdsrState.Off)
                {

                    op1Output = this.op1.GetOperatorOutput(feedbackOutput);
                    var op2Output = this.op2.GetOperatorOutput();
                    var op3Output = this.op3.GetOperatorOutput(op2Output * 4);
                    var op4Output = this.op4.GetOperatorOutput(op3Output * 4);

                    channelOutput = (op1Output + op4Output) / 2;
                }
                else
                    return 0;
                break;

            case 3:
                if((this.op1.AdsrState | this.op3.AdsrState | this.op4.AdsrState) != AdsrState.Off)
                {

                    op1Output = this.op1.GetOperatorOutput(feedbackOutput);
                    var op2Output = this.op2.GetOperatorOutput();
                    var op3Output = this.op3.GetOperatorOutput(op2Output * 4);
                    var op4Output = this.op4.GetOperatorOutput();

                    channelOutput = (op1Output + op3Output + op4Output) / 3;
                }
                else
                    return 0;
                break;
            }

            this.previousFeedback = this.currentFeedback;
            this.currentFeedback = this.GetFeedbackValue(op1Output);

            return channelOutput;
        }
        /// <summary>
        /// Activates channel output.
        /// </summary>
        protected override void KeyOn()
        {
            this.op1.KeyOn();
            this.op2.KeyOn();
            this.op3.KeyOn();
            this.op4.KeyOn();
            this.previousFeedback = 0;
            this.currentFeedback = 0;
        }
        /// <summary>
        /// Disables channel output.
        /// </summary>
        protected override void KeyOff()
        {
            this.op1.KeyOff();
            this.op2.KeyOff();
            this.op3.KeyOff();
            this.op4.KeyOff();
        }
        /// <summary>
        /// Updates the state of all of the operators in the channel.
        /// </summary>
        protected override void UpdateOperators()
        {
            int keyScaleNumber = this.KeyScaleNumber;
            this.op1.SetChannelData(this.FrequencyNumber, keyScaleNumber, this.Block);
            this.op2.SetChannelData(this.FrequencyNumber, keyScaleNumber, this.Block);
            this.op3.SetChannelData(this.FrequencyNumber, keyScaleNumber, this.Block);
            this.op4.SetChannelData(this.FrequencyNumber, keyScaleNumber, this.Block);
        }
    }
}
